﻿//  Copyright (c) Microsoft Corporation.  All rights reserved.

namespace Microsoft.VisualStudio.TestTools.WorkItemMigrator
{
    using System;
    using System.Collections.Generic;
    using System.Xml;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;

    /// <summary>
    /// It is Internal represents a field of TFS Workitem
    /// </summary>
    internal class WorkItemField : IWorkItemField
    {
        #region Constants

        // Reference name of Created Date field
        private const string CreatedDateReferenceName = "System.CreatedDate";

        // Reference name of Area path field
        private const string AreaPathRefernceName = "System.AreaPath";

        // Reference name of Iteration Path field
        private const string IterationPathReferenceName = "System.IterationPath";

        // Reference name of Area Id field
        private const string AreaIDReferenceName = "System.AreaId";

        // Reference name of Iteration Id field
        private const string IterationIDReferenceName = "System.IterationId";

        // Reference name of State field
        private const string StateReferenceName = "System.State";

        // Reference name of Title field
        private const string TitleReferenceName = "System.Title";

        // Reference name of Title field
        private const string IDReferenceName = "System.Id";

        // Reference name of History field
        private const string HistoryReferenceName = "System.History";

        // Reference name of History field
        private const string StepsReferenceName = "Microsoft.VSTS.TCM.Steps";

        #endregion

        #region Fields

        private Field m_field;
        private string m_sourceName;

        // TFS representaion of Field Defination
        private FieldDefinition m_fieldDefinition;

        #endregion

        #region Constructor

        /// <summary>
        /// Basic Constructor would be used by derived Work item fields
        /// </summary>
        protected WorkItemField()
        { }

        /// <summary>
        /// Main Constructor
        /// </summary>
        /// <param name="field"></param>
        /// <param name="project"></param>
        public WorkItemField(Field field, Project project)
        {
            m_field = field;

            bool hasDefaultValue = field.Value != null &&
                                    String.CompareOrdinal(field.Value.ToString(), "As a <type of user> I want <some goal> so that <some reason>") != 0 &&
                                    !string.IsNullOrEmpty(field.Value.ToString());

            m_fieldDefinition = field.FieldDefinition;

            TfsName = m_fieldDefinition.Name;
            HelpText = m_fieldDefinition.HelpText;

            // A field is mandatory if it is required but does not have any default value
            // Exception: Created Date is required field and also does not have any default value 
            //            but at the time of save it populates some default value. So it is also not mandatory field
            IsMandatory = false;
            if (field.IsRequired &&
                !hasDefaultValue &&
                String.CompareOrdinal(field.ReferenceName, CreatedDateReferenceName) != 0)
            {
                IsMandatory = true;
            }

            // A field is autogenerated if it is not editable 
            // Exception: Area and iteration Ids are editable but they are changed automatically whenevr correspong paths are changed
            IsAutoGenerated = !field.IsEditable ||
                              (String.CompareOrdinal(m_fieldDefinition.ReferenceName, AreaIDReferenceName) == 0) ||
                              (String.CompareOrdinal(m_fieldDefinition.ReferenceName, IterationIDReferenceName) == 0);

            // Initialize the allowed values for this field
            InitializeAllowedValues(project, field);

            ValueMapping = new Dictionary<string, string>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// FIeld name found in data source
        /// </summary>
        public string SourceName
        {
            get
            {
                return m_sourceName;
            }
            set
            {
                m_sourceName = value;
                if (HasAllowedValues && string.IsNullOrEmpty(m_sourceName))
                {
                    ValueMapping.Clear();
                }
            }
        }

        /// <summary>
        /// One on one mapping from source value to TFS Allowed Value
        /// </summary>
        public IDictionary<string, string> ValueMapping
        {
            get;
            protected set;
        }

        /// <summary>
        /// Workitem Field Name
        /// </summary>
        public string TfsName
        {
            get;
            protected set;
        }

        /// <summary>
        /// Help Text of this field
        /// </summary>
        public string HelpText
        {
            get;
            protected set;
        }

        /// <summary>
        /// Mandatory fields are those fields whose value can't be left empty for save
        /// </summary>
        public bool IsMandatory
        {
            get;
            protected set;
        }

        /// <summary>
        /// Are the values for this field autogenerated?
        /// </summary>
        public bool IsAutoGenerated
        {
            get;
            protected set;
        }

        /// <summary>
        /// Is this field has Allowed Values?
        /// </summary>
        public bool HasAllowedValues
        {
            get
            {
                return ((AllowedValues != null) && (AllowedValues.Count > 0));
            }
        }

        /// <summary>
        /// List of allowed Values if this field supports
        /// </summary>
        public IList<string> AllowedValues
        {
            get;
            protected set;
        }

        /// <summary>
        /// Default TFS value of field
        /// </summary>
        public string DefaultValue
        {
            get
            {
                if (HasAllowedValues && m_field.OriginalValue != null)
                {
                    return m_field.OriginalValue.ToString();
                }
                return null;
            }
        }

        /// <summary>
        /// Returns the ssytem type of Work item field
        /// </summary>
        public Type Type
        {
            get
            {
                return m_fieldDefinition != null ? m_fieldDefinition.SystemType : typeof(System.Reflection.Missing);
            }
        }

        /// <summary>
        /// Is this TFS Title Field
        /// </summary>
        public bool IsTitleField
        {
            get
            {
                return m_field != null &&
                    String.CompareOrdinal(m_field.ReferenceName, TitleReferenceName) == 0;
            }
        }

        /// <summary>
        /// is this field supports html value
        /// </summary>
        public bool IsHtmlField
        {
            get
            {
                // Bug
                return m_field != null &&
                       String.CompareOrdinal(m_field.ReferenceName, HistoryReferenceName) == 0;
            }
        }

        /// <summary>
        /// Is TCM Steps Field?
        /// </summary>
        public bool IsStepsField
        {
            get
            {
                return m_field != null &&
                    String.CompareOrdinal(m_field.ReferenceName, StepsReferenceName) == 0;
            }
        }

        /// <summary>
        /// Is TFS ID Field?
        /// </summary>
        public bool IsIdField
        {
            get
            {
                return m_field != null &&
                    String.CompareOrdinal(m_field.ReferenceName, IDReferenceName) == 0;
            }
        }

        public bool IsAreaPath
        {
            get
            {
                return m_fieldDefinition != null &&
                       String.CompareOrdinal(m_fieldDefinition.ReferenceName, AreaPathRefernceName) == 0;
            }
        }

        public bool IsIterationPath
        {
            get
            {
                return m_fieldDefinition != null &&
                       String.CompareOrdinal(m_fieldDefinition.ReferenceName, IterationPathReferenceName) == 0;
            }
        }
        #endregion

        #region private methods

        /// <summary>
        /// Fills the allowed values if it supports allowed values
        /// </summary>
        /// <param name="project"></param>
        /// <param name="field"></param>
        private void InitializeAllowedValues(Project project, Field field)
        {
            // If this is State Field, then take only those states which are supported at creation time.
            if (String.CompareOrdinal(m_fieldDefinition.ReferenceName, StateReferenceName) == 0)
            {
                FillAllowedStatesAtCreationTime(field.WorkItem);
            }
            // else if this is area path then fill allowed area paths
            else if (m_fieldDefinition.ReferenceName == AreaPathRefernceName)
            {
                FillPaths(project.Name, project.AreaRootNodes);
            }
            // else if this is iteration path then fill allowed iteration paths
            else if (m_fieldDefinition.ReferenceName == IterationPathReferenceName)
            {
                FillPaths(project.Name, project.IterationRootNodes);
            }
            else if (m_fieldDefinition.AllowedValues.Count > 0)
            {
                AllowedValues = new List<string>();
                foreach (string allowedvalue in m_fieldDefinition.AllowedValues)
                {
                    AllowedValues.Add(allowedvalue);
                }
            }
        }

        /// <summary>
        /// Fill Area/Iteration Path in Allowed Values
        /// </summary>
        /// <param name="projectName"></param>
        /// <param name="collection"></param>
        private void FillPaths(string projectName, NodeCollection collection)
        {
            AllowedValues = new List<string>();

            // Adding project Name in Allowed Values
            AllowedValues.Add(projectName);

            // Filling other Values
            foreach (string areaPaths in GetPaths(collection))
            {
                AllowedValues.Add(areaPaths);
            }
        }

        /// <summary>
        /// Converts the Tree Structure of NodeCollection into List of paths from root to every other node of tree
        /// </summary>
        /// <param name="nodeCollection"></param>
        /// <returns></returns>
        private List<string> GetPaths(NodeCollection nodeCollection)
        {
            List<string> paths = new List<string>();
            foreach (Node node in nodeCollection)
            {
                // Adding this node to list of Paths
                paths.Add(node.Path);

                // If this node has childs then Add the path of every child node from this node in the list
                if (node.HasChildNodes)
                {
                    foreach (string iterationPath in GetPaths(node.ChildNodes))
                    {
                        paths.Add(iterationPath);
                    }
                }
            }
            return paths;
        }


        /// <summary>
        /// Fill Allowed States at Creation time in Allowed Values
        /// </summary>
        /// <param name="wi"></param>
        private void FillAllowedStatesAtCreationTime(WorkItem wi)
        {
            //HasAllowedValues = true;
            AllowedValues = new List<string>();

            WorkItemType wit = wi.Type;
            XmlDocument witd = wit.Export(true);

            // retrieve the transitions node
            XmlNode transitionsNode = witd.SelectSingleNode("descendant::TRANSITIONS");

            if (transitionsNode != null)
            {
                // Get all the state TRANSITION nodes from the WIT template.
                XmlNodeList transitionNodes = transitionsNode.SelectNodes("TRANSITION");
                if (transitionNodes != null)
                {
                    // Iterate all the possible transitions and filter those that are valid
                    // transitions from our current state.
                    foreach (XmlNode transitionNode in transitionNodes)
                    {
                        // Get the FROM and TO attributes from the transisitiion node.
                        XmlNode fromAttribute = transitionNode.Attributes.GetNamedItem("from");
                        XmlNode toAttribute = transitionNode.Attributes.GetNamedItem("to");

                        if (fromAttribute != null)
                        {
                            // Add this transition to the list of valid transitions if it is a
                            // transition from our current state.
                            string fromValue = fromAttribute.Value;
                            if (string.IsNullOrWhiteSpace(fromValue) && toAttribute != null)
                            {
                                AllowedValues.Add(toAttribute.Value);
                            }
                        }
                    }
                }
            }
        }

        #endregion
    }

    /// <summary>
    /// Test Step Title Field
    /// </summary>
    internal class TestStepTitleField : WorkItemField
    {
        public TestStepTitleField()
        {
            TfsName = "Test Step Title";
            HelpText = Resources.TestStepTitleField_HelpText;
            IsAutoGenerated = false;
            IsMandatory = false;
            ValueMapping = new Dictionary<string, string>();
        }
    }

    /// <summary>
    /// Test Step Expected result Field
    /// </summary>
    internal class TestStepExpectedResultField : WorkItemField
    {
        public TestStepExpectedResultField()
        {
            TfsName = "Test Step Expected Result";
            HelpText = Resources.TestStepExpectedResultField_HelpText;
            IsAutoGenerated = false;
            IsMandatory = false;
            ValueMapping = new Dictionary<string, string>();
        }
    }
}
